//----------------------------------------------------------------------------------------------------------------------
// Copyright (c) 2013 James Whitworth
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//----------------------------------------------------------------------------------------------------------------------
#include <sqtest/sqtest.h>
//----------------------------------------------------------------------------------------------------------------------
#include <gtest/gtest.h>
//----------------------------------------------------------------------------------------------------------------------
#include <sqtest/sqtScript.h>
#include <sqtest/sqtScriptPredicateHelper.h>
#include <sqtest/sqtScriptRegistryTableHelper.h>
#include <sqtest/sqtTest.h>
//----------------------------------------------------------------------------------------------------------------------

using namespace sqt;

//----------------------------------------------------------------------------------------------------------------------
/// \def SQTEST_UNUSED
/// \brief Marks a function argument as unused
//----------------------------------------------------------------------------------------------------------------------
#define SQTEST_UNUSED(X)

//----------------------------------------------------------------------------------------------------------------------
/// \def SQTEST_WARNING_PUSH
/// \brief Used to disable msvc compiler warnings within macros.
//----------------------------------------------------------------------------------------------------------------------
#if defined(_MSC_VER)
# define SQTEST_WARNING_PUSH(SQTEST_WARNINGS) \
  __pragma(warning(push)) \
  __pragma(warning(SQTEST_WARNINGS))
#else
# define SQTEST_WARNING_PUSH(SQTEST_WARNINGS)
#endif // defined(_MSC_VER)

//----------------------------------------------------------------------------------------------------------------------
/// \def SQTEST_WARNING_POP
/// \brief Used to disable msvc compiler warnings within macros.
//----------------------------------------------------------------------------------------------------------------------
#if defined(_MSC_VER)
# define SQTEST_WARNING_POP() \
  __pragma(warning(pop))
#else
# define SQTEST_WARNING_POP()
#endif // defined(_MSC_VER)

typedef std::basic_string<SQChar, std::char_traits<SQChar>, std::allocator<SQChar> > string;

const SQChar *kGTestFatalFailureString = _SC("gtest-fatal-failure");

//----------------------------------------------------------------------------------------------------------------------
#define SQTEST_IMPL_BOOL_FUNC(func_name, expected_bool, failure_type) \
  SQInteger sqtest_ ## func_name (HSQUIRRELVM vm) \
  { \
    SQBool result = SQTrue; \
    sq_getbool(vm, 2, &result); \
    if (const ::testing::AssertionResult gtest_ar_ = ::testing::AssertionResult((result == expected_bool) ? true : false)) \
    { \
      /* do nothing */ \
    } \
    else \
    { \
      SQStackInfos si; \
      si.funcname = _SC("unknown"); \
      si.source = _SC("unknown"); \
      si.line = 0; \
      sq_stackinfos(vm, 1, &si); \
       \
      std::string expression_text; \
       \
      auto script = ScriptRegistryTableHelper::GetScript(vm, si.source); \
      if (script != nullptr) \
      { \
        ScriptPredicateHelper::GetPredicateText1(vm, *script, static_cast<size_t>(si.line), &expression_text); \
      } \
       \
      /* log the failure */ \
      ::testing::internal::String message = \
        ::testing::internal::GetBoolAssertionFailureMessage(gtest_ar_, expression_text.c_str(), (expected_bool == SQTrue) ? "false" : "true", (expected_bool == SQTrue) ? "true" : "false"); \
      auto sourceUtf8 = ScriptPredicateHelper::ConvertStringUtf8(si.source); \
      ::testing::internal::AssertHelper(failure_type, sourceUtf8.c_str(), static_cast<int>(si.line), message.c_str()) = ::testing::Message(); \
       \
      SQTEST_WARNING_PUSH(disable : 4127) \
      if (failure_type == ::testing::TestPartResult::kFatalFailure) \
      SQTEST_WARNING_POP() \
      { \
        return sq_throwerror(vm, kGTestFatalFailureString); \
      } \
    } \
     \
    return 0; \
  }

SQTEST_IMPL_BOOL_FUNC(expect_true,  SQTrue,  ::testing::TestPartResult::kNonFatalFailure);
SQTEST_IMPL_BOOL_FUNC(assert_true,  SQTrue,  ::testing::TestPartResult::kFatalFailure);
SQTEST_IMPL_BOOL_FUNC(expect_false, SQFalse, ::testing::TestPartResult::kNonFatalFailure);
SQTEST_IMPL_BOOL_FUNC(assert_false, SQFalse, ::testing::TestPartResult::kFatalFailure);

//----------------------------------------------------------------------------------------------------------------------
#define SQTEST_IMPL_CMP_FUNC(op_name, op_type, failure_type) \
  SQInteger sqtest_ ## op_name (HSQUIRRELVM vm) \
  { \
    if (0 op_type sq_cmp(vm)) \
    { \
    } \
    else \
    { \
      SQStackInfos si; \
      si.funcname = _SC("unknown"); \
      si.source = _SC("unknown"); \
      si.line = 0; \
      sq_stackinfos(vm, 1, &si); \
       \
      std::string expected_expression_text; \
      std::string actual_expression_text; \
       \
      auto script = ScriptRegistryTableHelper::GetScript(vm, si.source); \
      if (script != nullptr) \
      { \
        ScriptPredicateHelper::GetPredicateText2(vm, *script, static_cast<size_t>(si.line), &expected_expression_text, &actual_expression_text); \
      } \
       \
      sq_tostring(vm, 2); \
      const SQChar* expected_value_text = _SC(""); \
      sq_getstring(vm, -1, &expected_value_text); \
       \
      sq_tostring(vm, 3); \
      const SQChar* actual_value_text = _SC(""); \
      sq_getstring(vm, -1, &actual_value_text); \
       \
      ::testing::internal::String message = ::testing::internal::String::Format( \
        "Expected: (%s) " #op_type " (%s), actual %s vs %s", \
        expected_expression_text.c_str(), \
        actual_expression_text.c_str(), \
        expected_value_text, \
        actual_value_text); \
      auto sourceUtf8 = ScriptPredicateHelper::ConvertStringUtf8(si.source); \
      ::testing::internal::AssertHelper(failure_type, sourceUtf8.c_str(), static_cast<int>(si.line), message.c_str()) = ::testing::Message(); \
       \
      sq_pop(vm, 2); \
       \
      SQTEST_WARNING_PUSH(disable : 4127) \
      if (failure_type == ::testing::TestPartResult::kFatalFailure) \
      SQTEST_WARNING_POP() \
      { \
        return sq_throwerror(vm, kGTestFatalFailureString); \
      } \
    } \
    return 0; \
  }

SQTEST_IMPL_CMP_FUNC(expect_eq, ==, ::testing::TestPartResult::kNonFatalFailure);
SQTEST_IMPL_CMP_FUNC(assert_eq, ==, ::testing::TestPartResult::kFatalFailure);
SQTEST_IMPL_CMP_FUNC(expect_ne, !=, ::testing::TestPartResult::kNonFatalFailure);
SQTEST_IMPL_CMP_FUNC(assert_ne, !=, ::testing::TestPartResult::kFatalFailure);
SQTEST_IMPL_CMP_FUNC(expect_lt, < , ::testing::TestPartResult::kNonFatalFailure);
SQTEST_IMPL_CMP_FUNC(assert_lt, < , ::testing::TestPartResult::kFatalFailure);
SQTEST_IMPL_CMP_FUNC(expect_le, <=, ::testing::TestPartResult::kNonFatalFailure);
SQTEST_IMPL_CMP_FUNC(assert_le, <=, ::testing::TestPartResult::kFatalFailure);
SQTEST_IMPL_CMP_FUNC(expect_gt, > , ::testing::TestPartResult::kNonFatalFailure);
SQTEST_IMPL_CMP_FUNC(assert_gt, > , ::testing::TestPartResult::kFatalFailure);
SQTEST_IMPL_CMP_FUNC(expect_ge, >=, ::testing::TestPartResult::kNonFatalFailure);
SQTEST_IMPL_CMP_FUNC(assert_ge, >=, ::testing::TestPartResult::kFatalFailure);

#ifdef SQUNICODE
# define SQTEST_STRING_EQ              testing::internal::String::WideCStringEquals
# define SQTEST_STRING_NE             !testing::internal::String::WideCStringEquals
# define SQTEST_INSENSITIVE_STRING_EQ  testing::internal::String::CaseInsensitiveWideCStringEquals
# define SQTEST_INSENSITIVE_STRING_NE !testing::internal::String::CaseInsensitiveWideCStringEquals
# define SQTEST_SHOW_STRING_QUOTED     testing::internal::String::ShowWideCStringQuoted
#else
# define SQTEST_STRING_EQ              testing::internal::String::CStringEquals
# define SQTEST_STRING_NE             !testing::internal::String::CStringEquals
# define SQTEST_INSENSITIVE_STRING_EQ  testing::internal::String::CaseInsensitiveCStringEquals
# define SQTEST_INSENSITIVE_STRING_NE !testing::internal::String::CaseInsensitiveCStringEquals
# define SQTEST_SHOW_STRING_QUOTED     testing::internal::String::ShowCStringQuoted
#endif

//----------------------------------------------------------------------------------------------------------------------
#define SQTEST_IMPL_STR_FUNC(op_name, op_type, op_symbol, failure_type) \
  SQInteger sqtest_ ## op_name (HSQUIRRELVM vm) \
  { \
    const SQChar* expected = 0; \
    sq_getstring(vm, 2, &expected); \
     \
    const SQChar* actual = 0; \
    sq_getstring(vm, 3, &actual); \
     \
    if (op_type(expected, actual)) \
    { \
    } \
    else \
    { \
      SQStackInfos si; \
      si.funcname = _SC("unknown"); \
      si.source = _SC("unknown"); \
      si.line = 0; \
      sq_stackinfos(vm, 1, &si); \
       \
      std::string expected_expression_text; \
      std::string actual_expression_text; \
      \
      auto script = ScriptRegistryTableHelper::GetScript(vm, si.source); \
      if (script != nullptr) \
      { \
        ScriptPredicateHelper::GetPredicateText2(vm, *script, static_cast<size_t>(si.line), &expected_expression_text, &actual_expression_text); \
      } \
       \
      sq_tostring(vm, 2); \
      const SQChar* expected_value_text = _SC(""); \
      sq_getstring(vm, -1, &expected_value_text); \
       \
      sq_tostring(vm, 3); \
      const SQChar* actual_value_text = _SC(""); \
      sq_getstring(vm, -1, &actual_value_text); \
       \
      ::testing::internal::String message = ::testing::internal::String::Format( \
        "Expected: (%s) " #op_symbol " (%s), actual %s vs %s", \
        expected_expression_text.c_str(), \
        actual_expression_text.c_str(), \
        SQTEST_SHOW_STRING_QUOTED(expected_value_text).c_str(), \
        SQTEST_SHOW_STRING_QUOTED(actual_value_text).c_str()); \
      auto sourceUtf8 = ScriptPredicateHelper::ConvertStringUtf8(si.source); \
      ::testing::internal::AssertHelper(failure_type, sourceUtf8.c_str(), static_cast<int>(si.line), message.c_str()) = ::testing::Message(); \
       \
      sq_pop(vm, 2); \
       \
      SQTEST_WARNING_PUSH(disable : 4127) \
      if (failure_type == ::testing::TestPartResult::kFatalFailure) \
      SQTEST_WARNING_POP() \
      { \
        return sq_throwerror(vm, kGTestFatalFailureString); \
      } \
    } \
    return 0; \
  }

SQTEST_IMPL_STR_FUNC(expect_streq, SQTEST_STRING_EQ, ==, ::testing::TestPartResult::kNonFatalFailure);
SQTEST_IMPL_STR_FUNC(assert_streq, SQTEST_STRING_EQ, ==, ::testing::TestPartResult::kFatalFailure);
SQTEST_IMPL_STR_FUNC(expect_strne, SQTEST_STRING_NE, !=, ::testing::TestPartResult::kNonFatalFailure);
SQTEST_IMPL_STR_FUNC(assert_strne, SQTEST_STRING_NE, !=, ::testing::TestPartResult::kFatalFailure);
SQTEST_IMPL_STR_FUNC(expect_strcaseeq, SQTEST_INSENSITIVE_STRING_EQ, ==, ::testing::TestPartResult::kNonFatalFailure);
SQTEST_IMPL_STR_FUNC(assert_strcaseeq, SQTEST_INSENSITIVE_STRING_EQ, ==, ::testing::TestPartResult::kFatalFailure);
SQTEST_IMPL_STR_FUNC(expect_strcasene, SQTEST_INSENSITIVE_STRING_NE, !=, ::testing::TestPartResult::kNonFatalFailure);
SQTEST_IMPL_STR_FUNC(assert_strcasene, SQTEST_INSENSITIVE_STRING_NE, !=, ::testing::TestPartResult::kFatalFailure);

#ifdef SQUSEDOUBLE
# define SQTEST_FLT_FMT "%0.17f"
#else
# define SQTEST_FLT_FMT "%0.8f"
#endif

//----------------------------------------------------------------------------------------------------------------------
#define SQTEST_IMPL_FLOAT_EQ_FUNC(op_name, failure_type) \
  SQInteger sqtest_ ## op_name (HSQUIRRELVM vm) \
  { \
    SQFloat expected = static_cast<SQFloat>(0.0); \
    sq_getfloat(vm, 2, &expected); \
     \
    SQFloat actual = static_cast<SQFloat>(0.0); \
    sq_getfloat(vm, 3, &actual); \
     \
    const ::testing::internal::FloatingPoint<SQFloat> lhs(expected), rhs(actual); \
    if (lhs.AlmostEquals(rhs)) \
    { \
    } \
    else \
    { \
      SQStackInfos si; \
      si.funcname = _SC("unknown"); \
      si.source = _SC("unknown"); \
      si.line = 0; \
      sq_stackinfos(vm, 1, &si); \
       \
      std::string expected_expression_text; \
      std::string actual_expression_text; \
       \
      auto script = ScriptRegistryTableHelper::GetScript(vm, si.source); \
      if (script != nullptr) \
      { \
        ScriptPredicateHelper::GetPredicateText2(vm, *script, static_cast<size_t>(si.line), &expected_expression_text, &actual_expression_text); \
      } \
       \
      ::testing::internal::String message = ::testing::internal::String::Format( \
      "Expected: (%s) == (%s), actual " SQTEST_FLT_FMT " vs " SQTEST_FLT_FMT, \
        expected_expression_text.c_str(), \
        actual_expression_text.c_str(), \
        expected, \
        actual); \
      auto sourceUtf8 = ScriptPredicateHelper::ConvertStringUtf8(si.source); \
      ::testing::internal::AssertHelper(failure_type, sourceUtf8.c_str(), static_cast<int>(si.line), message.c_str()) = ::testing::Message(); \
       \
      sq_pop(vm, 2); \
       \
      SQTEST_WARNING_PUSH(disable : 4127) \
      if (failure_type == ::testing::TestPartResult::kFatalFailure) \
      SQTEST_WARNING_POP() \
      { \
        return sq_throwerror(vm, kGTestFatalFailureString); \
      } \
    } \
    return 0; \
  }

SQTEST_IMPL_FLOAT_EQ_FUNC(expect_float_eq, ::testing::TestPartResult::kNonFatalFailure);
SQTEST_IMPL_FLOAT_EQ_FUNC(assert_float_eq, ::testing::TestPartResult::kFatalFailure);

//----------------------------------------------------------------------------------------------------------------------
#define _DECL_FUNC(name,nparams,pmask) { _SC(#name), sqtest_##name, nparams, pmask }
static SQRegFunction sqtest_lib_funcs[] = {
  _DECL_FUNC(expect_true, 2, _SC(".b")),
  _DECL_FUNC(assert_true, 2, _SC(".b")),
  _DECL_FUNC(expect_false, 2, _SC(".b")),
  _DECL_FUNC(assert_false, 2, _SC(".b")),
  _DECL_FUNC(expect_eq, 3, _SC("...")),
  _DECL_FUNC(assert_eq, 3, _SC("...")),
  _DECL_FUNC(expect_ne, 3, _SC("...")),
  _DECL_FUNC(assert_ne, 3, _SC("...")),
  _DECL_FUNC(expect_lt, 3, _SC("...")),
  _DECL_FUNC(assert_lt, 3, _SC("...")),
  _DECL_FUNC(expect_le, 3, _SC("...")),
  _DECL_FUNC(assert_le, 3, _SC("...")),
  _DECL_FUNC(expect_gt, 3, _SC("...")),
  _DECL_FUNC(assert_gt, 3, _SC("...")),
  _DECL_FUNC(expect_ge, 3, _SC("...")),
  _DECL_FUNC(assert_ge, 3, _SC("...")),
  _DECL_FUNC(expect_streq, 3, _SC(".ss")),
  _DECL_FUNC(assert_streq, 3, _SC(".ss")),
  _DECL_FUNC(expect_strne, 3, _SC(".ss")),
  _DECL_FUNC(assert_strne, 3, _SC(".ss")),
  _DECL_FUNC(expect_strcaseeq, 3, _SC(".ss")),
  _DECL_FUNC(assert_strcaseeq, 3, _SC(".ss")),
  _DECL_FUNC(expect_strcasene, 3, _SC(".ss")),
  _DECL_FUNC(assert_strcasene, 3, _SC(".ss")),
  _DECL_FUNC(expect_float_eq, 3, _SC(".ff")),
  _DECL_FUNC(assert_float_eq, 3, _SC(".ff")),
  { 0, 0, 0, 0 }
};

//----------------------------------------------------------------------------------------------------------------------
void sqtest_compile_error(
  HSQUIRRELVM SQTEST_UNUSED(vm),
  const SQChar *desc,
  const SQChar *source,
  SQInteger line,
  SQInteger SQTEST_UNUSED(column))
{
  auto sourceUtf8 = ScriptPredicateHelper::ConvertStringUtf8(source);
  auto descUtf8 = ScriptPredicateHelper::ConvertStringUtf8(desc);
  ::testing::internal::AssertHelper(
    ::testing::TestPartResult::kFatalFailure,
    sourceUtf8.c_str(),
    static_cast<int>(line),
    descUtf8.c_str()) = ::testing::Message();
}

//----------------------------------------------------------------------------------------------------------------------
SQInteger sqtest_runtime_error(HSQUIRRELVM vm)
{
  sq_tostring(vm, 2);
  auto errorString = _SC("Failure");
  sq_getstring(vm, -1, &errorString);

  if (scstrcmp(errorString, kGTestFatalFailureString) == 0)
  {
    return 0;
  }

  SQStackInfos stackinfos;
  stackinfos.funcname = _SC("unknown");
  stackinfos.source = _SC("unknown");
  stackinfos.line = 0;
  sq_stackinfos(vm, 1, &stackinfos);

  auto sourceUtf8 = ScriptPredicateHelper::ConvertStringUtf8(stackinfos.source);
  auto errorStringUtf8 = ScriptPredicateHelper::ConvertStringUtf8(errorString);

  ::testing::internal::AssertHelper(
    ::testing::TestPartResult::kFatalFailure,
    sourceUtf8.c_str(),
    static_cast<int>(stackinfos.line),
    errorStringUtf8.c_str()) = ::testing::Message();

  // pop the string created by sq_tostring
  sq_pop(vm, 1);

  return 0;
}

//----------------------------------------------------------------------------------------------------------------------
SQRESULT sqtest_register_lib(HSQUIRRELVM vm)
{
  SQRESULT result = SQ_OK;
  SQInteger i = 0;
  while (sqtest_lib_funcs[i].name!=0)
  {
    sq_pushstring(vm, sqtest_lib_funcs[i].name, -1);
    sq_newclosure(vm, sqtest_lib_funcs[i].f, 0);
    if (SQ_FAILED(sq_setparamscheck(vm, sqtest_lib_funcs[i].nparamscheck, sqtest_lib_funcs[i].typemask)))
    {
      result = SQ_ERROR;
    }
    sq_setnativeclosurename(vm,-1,sqtest_lib_funcs[i].name);
    sq_createslot(vm, -3);
    ++i;
  }

  sq_setcompilererrorhandler(vm, &sqtest_compile_error);
  sq_newclosure(vm, &sqtest_runtime_error, 0);
  sq_seterrorhandler(vm);

  return result;
}

//----------------------------------------------------------------------------------------------------------------------
SQRESULT sqtest_addfile(const char *fileNameUtf8)
{
  auto script = sqt::LoadScriptAndParseContents(fileNameUtf8);
  if (!script)
  {
    return SQ_ERROR;
  }

  const auto result = sqt::TestFactory<sqt::Test>::RegisterTestCaseScript(fileNameUtf8, script);
  return result;
}
